---
title: Migration from v3 to v4
---

This is a summary of the changes necessary to migrate from SVGO v3 to SVGO v4. If you want more details or have any questions, please refer to our [release notes for SVGO v4.0.0](https://github.com/svg/svgo/releases/tag/v4.0.0) or related pull requests. You're encouraged to leave comments in pull requests if the existing content doesn't answer your question.

## Version Requirements

SVGO now requires [Node.js >=16.0.0](https://nodejs.org/blog/release/v16.0.0).

## Default Behavior

The following changes have been made to default plugins, also known as [preset-default](/docs/preset-default/):

- **[removeViewBox](/docs/plugins/removeViewBox/)** is no longer a default plugin to preserve scalability.
- **[removeTitle](/docs/plugins/removeTitle)** is no longer a default plugin to preserve accessibility.

To continue using removeViewBox or removeTitle, configure it in the SVGO config. See the [README](https://github.com/svg/svgo?tab=readme-ov-file#configuration) for more context, but please consider reading the warnings in the respective plugin's documentation first:

```diff
  export default {
    plugins: [
      'preset-default', // built-in plugins enabled by default
+     'removeViewBox',
+     'removeTitle',
    ],
  };
```

## Plugins

The removeScriptElement plugin has been renamed to **[removeScripts](/docs/plugins/removeScripts)**, as it not only removes the `<script>` element but also event handlers and script URIs. If you were using the removeScriptElement plugin, amend your SVGO config to use removeScripts instead:

```diff
  export default {
    plugins: [
      'preset-default', // built-in plugins enabled by default
-     'removeScriptElement',
+     'removeScripts',
    ],
  };
```

## Public vs. Internal API

We now enforce a boundary between public and internal APIs. It's no longer possible to arbitrarily import any code declared by SVGO, but rather only the public API we declare as part of our semantic versioning.

There are two ways to import SVGO:

- `svgo` — for normal usage, such as scripts or server-side applications.
- `svgo/browser` — for browser usage.

### Browser Bundle

If you use the browser bundle, you must amend how you import it:

```diff
- import { optimize } from 'svgo/dist/svgo.browser.js';
+ import { optimize } from 'svgo/browser';
```

### Importing Helpers or Plugins

If you `import`/`require` helpers, the array of built-in plugins, or a single plugin during runtime, this is now a top-level export instead:

```diff
// import helpers
- import { querySelector, querySelectorAll } from 'svgo/lib/xast.js';
+ import { querySelector, querySelectorAll } from 'svgo';

// import the array of built-in plugins
- import { builtin } from 'svgo/lib/builtin';
+ import { builtinPlugins } from 'svgo';

// getting a single plugin
- import presetDefault from 'svgo/plugins/preset-default';
+ import { builtinPlugins } from 'svgo';
+ const presetDefault = builtinPlugins.find(plugin => plugin.name === 'preset-default');

// getting a map of plugin names to implementations
import { builtinPlugins } from 'svgo';
const pluginMap = builtinPlugins.reduce((acc, val) => acc.set(val.name, val), new Map());
```

### Selector Helpers

Xast/CSS helpers for selecting nodes must be imported from `svgo` instead and have different behavior. This affects custom plugins that use any of the following functions, where the `selector` (2nd) argument could reference parent or sibling nodes (i.e. `div > span`):

- `querySelectorAll`
- `querySelector`
- `matches`

If this applies to you, then you need to pass a `Map` of nodes to their parent node as a third argument.

A helper has been provided named `#mapNodesToParents`, which does this for you. This can be used to easily migrate to the new API. If you're not sure if you need it, then it's safer to take this approach. The third argument won't be necessary if `selector` doesn't traverse nodes. For example, querying using one or more attributes of a single node.

```diff
- import { querySelectorAll } from 'svgo';
- const nodes = querySelectorAll(childNode, selector);
+ import { querySelectorAll, mapNodesToParents } from 'svgo';
+ const nodes = querySelectorAll(childNode, selector, mapNodesToParents(rootNode));
```

The new API for these functions is as follows:

```js
// applies `selector` with context of the `childNode` and its descendants
const nodes = querySelectorAll(childNode, selector);

// applies `selector` with context of the node tree relative from `childNode`
// `rootNode` is required if result depends on a parent/sibling of `childNode`
const nodes = querySelectorAll(childNode, selector, rootNode);

// same behavior as v3, as `rootNode` is already the entire node tree
const nodes = querySelectorAll(rootNode, selector);
```

## Named vs. Default Exports

SVGO now only has named exports—there are no default exports in the public API.

If you use SVGO from the CLI, have a Common JS project (you import SVGO using `require`), or are already using named exports, you don't have to do anything.

If one way or another you're using the `import` keyword to import SVGO, then any instance where you use the default export must be replaced with the named equivalent.

```diff
- import svgo from 'svgo';
- svgo.optimize('<svg></svg>');

// Option 1. Use named exports!
+ import { optimize } from 'svgo';
+ optimize('<svg></svg>');

// Option 2. Or import everything as svgo!
+ import * as svgo from 'svgo';
+ svgo.optimize('<svg></svg>');
```
